To run an optimization I need for one district (Neukölln?) - residential buildings ~> children per building ~> children age 5/6 per block - capacity per school

Load all base data

sampled_buildings = read_csv('output/sampled_buildings.csv')
Parsed with column specification:
cols(
  OI = col_character(),
  long = col_double(),
  lat = col_double(),
  BLK = col_character()
)
bez = readOGR('download/RBS_OD_BEZ_2015_12.geojson', layer = 'OGRGeoJSON')
OGR data source with driver: GeoJSON 
Source: "download/RBS_OD_BEZ_2015_12.geojson", layer: "OGRGeoJSON"
with 13 features
It has 2 fields
blk = readOGR('download/RBS_OD_BLK_2015_12.geojson', layer = 'OGRGeoJSON')
OGR data source with driver: GeoJSON 
Source: "download/RBS_OD_BLK_2015_12.geojson", layer: "OGRGeoJSON"
with 15720 features
It has 4 fields
lor = readOGR('download/RBS_OD_LOR_2015_12.geojson', layer = 'OGRGeoJSON')
OGR data source with driver: GeoJSON 
Source: "download/RBS_OD_LOR_2015_12.geojson", layer: "OGRGeoJSON"
with 447 features
It has 8 fields
re_schulstand = readOGR('download/re_schulstand.geojson', layer = 'OGRGeoJSON')
OGR data source with driver: GeoJSON 
Source: "download/re_schulstand.geojson", layer: "OGRGeoJSON"
with 709 features
It has 20 fields

Load the huge route matrix

routen_matrix = read_csv('output/routen_matrix.csv') %>% mutate(distance=distance*60)
Parsed with column specification:
cols(
  src = col_character(),
  dst = col_character(),
  duration = col_double(),
  distance = col_double()
)

Load school capacities and population data

TODO - neue Daten von Torres

kapas = read_csv('download/anmeldezahlen.csv') %>% filter(grepl('G', Schulnummer)) %>% filter(!is.na(`Plätze`))
Parsed with column specification:
cols(
  Bezirk = col_character(),
  Schulnummer = col_character(),
  Schulname = col_character(),
  Plätze = col_integer(),
  Anmeldungen = col_character()
)
einwohner_lor = read_delim('download/EWR201512E_Matrix.csv', delim=';')
Parsed with column specification:
cols(
  .default = col_character(),
  ZEIT = col_integer(),
  STADTRAUM = col_integer(),
  E_E = col_number(),
  E_EM = col_number(),
  E_EW = col_number(),
  E_E50_55 = col_number(),
  E_E25U55 = col_number(),
  E_E55U65 = col_number(),
  E_E65U80 = col_number()
)
See spec(...) for full column specifications.

Wo haben wir Kapazitäten?

re_schulstand_df = re_schulstand %>% as.data.frame() %>% rename(lon=coords.x1, lat=coords.x2)
re_schulstand_df %>% filter(grepl('G', spatial_name)) %>% mutate(BEZIRK=enc2utf8(as.character(BEZIRK))) %>%
  group_by(BEZIRK) %>% summarise(`Anzahl Schulen` = n()) %>%
  rename(Bezirk=BEZIRK) %>% left_join(kapas %>% group_by(Bezirk) %>% summarise(`Mit Kapazität` = n()))
Joining, by = "Bezirk"

Wir wählen einen Bezirk, wo wir von allen Schulen Kapas haben.

re_schulstand %>% as.data.frame() %>% filter(grepl('G', spatial_name)) %>% mutate(BEZIRK=enc2utf8(as.character(BEZIRK))) %>% group_by(BEZIRK) %>% summarise(`Anzahl Schulen` = n()) %>%
  rename(Bezirk=BEZIRK) %>% left_join(kapas %>% group_by(Bezirk) %>% summarise(`Mit Kapazität` = n())) %>% filter(`Anzahl Schulen` == `Mit Kapazität`)
Joining, by = "Bezirk"
bezirk = 'Tempelhof-Schöneberg'
schulen_mit_kapa = kapas %>% filter(Bezirk == bezirk) %>% .$Schulnummer
schulen_mit_kapa
 [1] "07G01" "07G02" "07G03" "07G05" "07G06" "07G07" "07G10" "07G12"
 [9] "07G13" "07G14" "07G15" "07G16" "07G17" "07G18" "07G19" "07G20"
[17] "07G21" "07G22" "07G23" "07G24" "07G25" "07G26" "07G27" "07G28"
[25] "07G29" "07G30" "07G31" "07G32" "07G34" "07G35" "07G36" "07G37"
07G01

07G02

07G03

07G05

07G06

07G07

07G10

07G12

07G13

07G14

07G15

07G16

07G17

07G18

07G19

07G20

07G21

07G22

07G23

07G24

07G25

07G26

07G27

07G28

07G29

07G30

07G31

07G32

07G34

07G35

07G36

07G37
grundschulen = re_schulstand %>% as.data.frame() %>% filter(grepl('G', spatial_name)) %>% filter(BEZIRK == bezirk) %>% .$spatial_name
'In Anmeldeliste, fehlt in Schulstand'
[1] "In Anmeldeliste, fehlt in Schulstand"
In Anmeldeliste, fehlt in Schulstand
setdiff(schulen_mit_kapa, grundschulen)
character(0)
'In re_schulstand, fehlt in Anmeldeliste'
[1] "In re_schulstand, fehlt in Anmeldeliste"
In re_schulstand, fehlt in Anmeldeliste
setdiff(grundschulen, schulen_mit_kapa)
character(0)

OMG nur bei Mitte mappt das perfekt! Alle anderen erfordern Nachforschungen.

map = get_map('Berlin')
Map from URL : http://maps.googleapis.com/maps/api/staticmap?center=Berlin&zoom=10&size=640x640&scale=2&maptype=terrain&language=en-EN&sensor=false
Information from URL : http://maps.googleapis.com/maps/api/geocode/json?address=Berlin&sensor=false
#re_schulstand[re_schulstand$spatial_name %in% setdiff(grundschulen, schulen_mit_anmeldungen),] %>% View()
re_schulstand_df = re_schulstand_df %>% left_join(kapas, by=c('spatial_name'='Schulnummer'))
Warning in left_join_impl(x, y, by$x, by$y, suffix$x, suffix$y): joining
character vector and factor, coercing into character vector
data = re_schulstand_df %>% filter(grepl('G', spatial_name)) %>% filter(BEZIRK==bezirk) %>% mutate(has.capa=is.na(`Plätze`))
ggmap(map) + geom_point(aes(lon, lat, color=has.capa), data=data) +
    coord_map(xlim=c(min(data$lon)-0.01, max(data$lon)+0.01), ylim=c(min(data$lat)-0.01, max(data$lat)+0.01))

Ignorieren das Problem und tun so, als ob nur diese Schulen relevant sind:

relevant_schools = re_schulstand_df %>% filter(grepl('G', spatial_name)) %>% filter(BEZIRK==bezirk & !is.na(`Plätze`)) %>% .$spatial_name
relevant_schools
 [1] "07G01" "07G02" "07G03" "07G05" "07G06" "07G07" "07G10" "07G12"
 [9] "07G13" "07G14" "07G15" "07G16" "07G17" "07G18" "07G19" "07G20"
[17] "07G21" "07G22" "07G23" "07G24" "07G25" "07G26" "07G27" "07G28"
[25] "07G29" "07G30" "07G31" "07G32" "07G34" "07G35" "07G36" "07G37"
07G01

07G02

07G03

07G05

07G06

07G07

07G10

07G12

07G13

07G14

07G15

07G16

07G17

07G18

07G19

07G20

07G21

07G22

07G23

07G24

07G25

07G26

07G27

07G28

07G29

07G30

07G31

07G32

07G34

07G35

07G36

07G37

Kinder im Bezirk auf Gebäude aufteilen

  1. LORs in Neuköln
  2. Finden aller Wohngebebäude in diesen LORs
  3. Über einwohler_lor finden wir die durchschnittliche Kinderzahl pro _Wohngebäude

Mapping Bezirk->LOR->Block

df_bez = as.data.frame(bez)
df_lor = as.data.frame(lor)
df_blk = as.data.frame(blk)

LORs im Bezirk

bez_id = filter(df_bez, BEZNAME == bezirk)$BEZ
relevant_lors = df_lor %>% filter(BEZ == bez_id)
relevant_blks = df_blk %>% filter(BEZ == bez_id)
ggplot() + geom_path(aes(x=long, y=lat, group=group), data=lor[lor$BEZ == bez_id,]) + coord_map() + geom_path(aes(x=long, y=lat, group=group), data=bez, color='red')
Regions defined for each Polygons
Regions defined for each Polygons

Blöcke im Bezirk

ggplot() + geom_path(aes(x=long, y=lat, group=group), data=blk[blk$BEZ == bez_id,]) + coord_map() + geom_path(aes(x=long, y=lat, group=group), data=bez[bez$BEZ == bez_id,], color='red')
Regions defined for each Polygons
Regions defined for each Polygons

Wohngebäude in diesen LORs

Schon erledigt in sampled_buildings.

Alter

TODO neue Daten von Torres

relevant_ewr = einwohner_lor %>% select(RAUMID, E_E06_07) %>% filter(RAUMID %in% relevant_lors$PLR) %>%
  mutate(kids=as.numeric(gsub(',','.',E_E06_07))) %>% as.data.frame()

data = tidy(lor[lor$BEZ == bez_id,], region='PLR') %>% inner_join(relevant_ewr, by=c('id'='RAUMID'))
ggmap(map) + geom_polygon(aes(x=long, y=lat, group=group, fill=kids), data=data) +
  coord_map(xlim=c(min(data$long)-0.01, max(data$long)+0.01), ylim=c(min(data$lat)-0.01, max(data$lat)+0.01))

Kids in BLKs

For each LOR distribute the children according to the number of people living in that block

kids_in_blks = relevant_blks %>% group_by(PLR) %>% mutate(EinwRatio = Einw/sum(Einw)) %>% ungroup %>% left_join(relevant_ewr, by=c('PLR'='RAUMID')) %>% mutate(kids = EinwRatio*kids) %>% select(BEZ, PLR, BLK, Einw, kids) %>% as.data.frame()
Warning in left_join_impl(x, y, by$x, by$y, suffix$x, suffix$y): joining
character vector and factor, coercing into character vector
row.names(kids_in_blks) = kids_in_blks$BLK

data = tidy(blk[blk$BEZ == bez_id,], region='BLK') %>% inner_join(kids_in_blks, by=c('id'='BLK')) %>% mutate(kids=ifelse(kids==0, NA, kids), Einw=ifelse(Einw==0, NA, Einw))
Warning in inner_join_impl(x, y, by$x, by$y, suffix$x, suffix$y): joining
character vector and factor, coercing into character vector
0
[1] 0
ggmap(map) + geom_polygon(aes(x=long, y=lat, group=group, fill=kids), data=data) +
  coord_map(xlim=c(min(data$long)-0.01, max(data$long)+0.01), ylim=c(min(data$lat)-0.01, max(data$lat)+0.01))

Kapas

relevant_kapas = kapas %>% select(Schulnummer, Kapa=`Plätze`) %>% filter(Schulnummer %in% relevant_schools) %>% as.data.frame()
#row.names(relevant_kapas) = relevant_kapas$Schulnummer

Sanity

'Summe Kapas'
[1] "Summe Kapas"
Summe Kapas
relevant_kapas %>% .$Kapa %>% sum
[1] 2584
'Summe Kapas + extra'
[1] "Summe Kapas + extra"
Summe Kapas + extra
relevant_kapas %>% .$Kapa_extra %>% sum
[1] 0
'Anmeldungen'
[1] "Anmeldungen"
Anmeldungen
kapas %>% mutate(Anmeldungen = as.numeric(gsub('[^0-9]', '', Anmeldungen))) %>% filter(Schulnummer %in% relevant_schools) %>% .$Anmeldungen %>% sum
[1] 2752
'Kids laut Statistik'
[1] "Kids laut Statistik"
Kids laut Statistik
kids_in_blks$kids %>% sum
[1] 2855
relevant_ewr$kids %>% sum
[1] 2855

Calculate mean distances to school per block

find block of each residential building

residential_buildings_blocks = sampled_buildings %>% inner_join(df_blk) %>% filter(BEZ == bez_id)
Joining, by = "BLK"
Warning in inner_join_impl(x, y, by$x, by$y, suffix$x, suffix$y): joining
character vector and factor, coercing into character vector
residential_buildings_blocks
routes_from_blks = residential_buildings_blocks %>%
  left_join(routen_matrix %>% filter(dst %in% relevant_schools), by=c('OI'='src'))
head(routes_from_blks)

Which blocks are relevant because there are residential buildings (TODO and someone lives there)

data = tidy(blk[blk$BEZ == bez_id,], region='BLK') %>% inner_join(routes_from_blks %>% group_by(BLK) %>% summarise(n=n()), by=c('id'='BLK'))
ggmap(map) + geom_polygon(aes(x=long, y=lat, group=group), fill='red', data=data) +
  #geom_point(aes(x=lon, y=lat), data=rb_df, color='black', size=0.01) +
  coord_map(xlim=c(min(data$long)-0.01, max(data$long)+0.01), ylim=c(min(data$lat)-0.01, max(data$lat)+0.01))

travel_from_blks = routes_from_blks %>% as.data.frame() %>% group_by(BLK, dst) %>% summarise(min=min(distance), avg=mean(distance), med=median(distance), max=max(distance)) %>% ungroup
travel_from_blks
travel_from_blks %>% write_csv('output/travel_from_blks.csv')

Color Blocks by avg distance

data = tidy(blk[blk$BEZ == bez_id,], region='BLK') %>% left_join(travel_from_blks %>% group_by(BLK) %>% top_n(1, -avg), by=c('id'='BLK'))
ggmap(map) + geom_polygon(aes(x=long, y=lat, group=group, fill=-avg), data=data) +
  geom_point(aes(lon, lat), color='red', data = re_schulstand_df %>% filter(BEZIRK==bezirk & SCHULART=='Grundschule')) +
  coord_map(xlim=c(min(data$long)-0.01, max(data$long)+0.01), ylim=c(min(data$lat)-0.01, max(data$lat)+0.01))

travel_matrix = travel_from_blks %>% select(BLK, dst, avg) %>% spread(dst, avg)
dim(travel_matrix)
[1] 1012   33
travel_matrix

Select relevant data

optim_kapas = relevant_kapas
optim_kids_in_blks = kids_in_blks %>% filter(kids > 0) %>% inner_join(travel_matrix, by='BLK') %>% select(BLK, kids) %>% mutate(kids=kids*0.88)
Warning in inner_join_impl(x, y, by$x, by$y, suffix$x, suffix$y): joining
factor and character vector, coercing into character vector
nrow(optim_kids_in_blks)
[1] 1012
nrow(optim_kapas)
[1] 32
select_schools = as.character(optim_kapas$Schulnummer)
select_blks = as.character(optim_kids_in_blks$BLK)

optim_matrix = inner_join(optim_kids_in_blks, travel_matrix, by='BLK')[select_schools]

dim(optim_matrix)
[1] 1012   32
optim_kapas$Kapa %>% sum
[1] 2584
optim_kids_in_blks$kids %>% sum
[1] 2503.5
optim_matrix %>% write_csv('output/optim_matrix.csv')
optim_kapas %>% write_csv('output/optim_kapas.csv')
optim_kids_in_blks %>% write_csv('output/optim_blks.csv')

Naive solution (that doesn’t obey capacity constraints)

solution = optim_matrix %>% mutate(BLK=optim_kids_in_blks$BLK) %>% gather(school, dist, -BLK) %>% group_by(BLK) %>% top_n(1, -dist) %>% ungroup

optim_matrix %>% t %>% as.data.frame %>% summarise_each(funs(min)) %>% sum()
[1] 47204364
solines = re_schulstand_df %>% inner_join(solution, by=c('spatial_name'='school')) %>% inner_join(cbind(as.data.frame(coordinates(blk[blk$BEZ == bez_id,])), blk[blk$BEZ == bez_id,]@data['BLK']))
Joining, by = "BLK"
Warning in inner_join_impl(x, y, by$x, by$y, suffix$x, suffix$y): joining
character vector and factor, coercing into character vector
data = tidy(blk[blk$BEZ == bez_id,], region='BLK') %>% left_join(solution, by=c('id'='BLK'))
ggmap(map, darken = c(0.5, 'white')) + geom_polygon(aes(x=long, y=lat, group=group, fill=school), data=data) +
  geom_segment(aes(x=V1,y=V2,xend=lon,yend=lat), data=solines, size=0.3) +
  geom_point(aes(lon, lat), color='black', size=2, data = re_schulstand_df %>% inner_join(solution, by=c('spatial_name'='school'))) +
  geom_point(aes(lon, lat, color=spatial_name), data = re_schulstand_df %>% inner_join(solution, by=c('spatial_name'='school'))) +
  coord_map(xlim=c(min(data$long)-0.01, max(data$long)+0.01), ylim=c(min(data$lat)-0.01, max(data$lat)+0.01)) +
  guides(color=F, fill=F)

solution %>% inner_join(travel_from_blks, by=c('BLK'='BLK', 'school'='dst')) %>% summarise_each("max", -BLK, -school, -dist)

JuMP

julia jump.jl
solution = read_csv('output/solution_jump.csv') %>% setNames(., select_schools) %>%
  mutate(BLK=select_blks) %>% gather(school, assigned, -BLK) %>% filter(assigned > 0)
Parsed with column specification:
cols(
  .default = col_double()
)
See spec(...) for full column specifications.
solines = re_schulstand_df %>% inner_join(solution, by=c('spatial_name'='school')) %>% inner_join(cbind(as.data.frame(coordinates(blk[blk$BEZ == bez_id,])), blk[blk$BEZ == bez_id,]@data['BLK']))
Joining, by = "BLK"
Warning in inner_join_impl(x, y, by$x, by$y, suffix$x, suffix$y): joining
character vector and factor, coercing into character vector
data = tidy(blk[blk$BEZ == bez_id,], region='BLK') %>% left_join(solution, by=c('id'='BLK'))
ggmap(map, darken = c(0.5, 'white')) + geom_polygon(aes(x=long, y=lat, group=group, fill=school), data=data) +
  geom_segment(aes(x=V1,y=V2,xend=lon,yend=lat), data=solines, size=0.3) +
  geom_point(aes(lon, lat), color='black', size=2, data = re_schulstand_df %>% inner_join(solution, by=c('spatial_name'='school'))) +
  geom_point(aes(lon, lat, color=spatial_name), data = re_schulstand_df %>% inner_join(solution, by=c('spatial_name'='school'))) +
  coord_map(xlim=c(min(data$long)-0.01, max(data$long)+0.01), ylim=c(min(data$lat)-0.01, max(data$lat)+0.01)) +
  guides(color=F, fill=F)

solution %>% inner_join(travel_from_blks, by=c('BLK'='BLK', 'school'='dst')) %>% summarise_all(max)
sum(read_csv('output/solution_jump.csv') * optim_matrix)
Parsed with column specification:
cols(
  .default = col_double()
)
See spec(...) for full column specifications.
[1] 50098818
sum(read_csv('output/solution_jump.csv') * optim_matrix * optim_kids_in_blks$kids)
Parsed with column specification:
cols(
  .default = col_double()
)
See spec(...) for full column specifications.
[1] 134794056

Display

library(formattable)
solution %>% inner_join(optim_kids_in_blks, by='BLK') %>% inner_join(travel_from_blks, by=c('BLK'='BLK', 'school'='dst')) %>%
  group_by(school) %>% summarise(
    kids=sum(kids),
    num_blocks=n(),
    min_dist=min(min),
    avg_dist=mean((kids*avg)/sum(kids)),
    max_dist=max(max)
  ) %>%
  inner_join(relevant_kapas, by=c('school'='Schulnummer')) %>%
  mutate(
    utilization=kids/Kapa
  ) %>% select(
   Schule=school,
   `Blöcke`=num_blocks,
   Kapazität=Kapa,
   Kinder=kids,
   Auslastung=utilization,
   `Weg (min)`=min_dist,
   `Weg (Ø)`=avg_dist,
   `Weg (max)`=max_dist
  ) %>%
  formattable(
    list(
      Kinder = formatter("span", x ~ digits(x, 2)),
      Auslastung = formatter("span",
        style = x ~ style(color = ifelse(x < 1, "green", "red")),
        x ~ icontext(ifelse(x < 1, "ok", "remove"), percent(x))
      ),
      `Weg (Ø)` = proportion_bar("lightblue"),
      `Weg (min)` = proportion_bar("lightblue"),
      `Weg (max)` = proportion_bar("lightblue")
    )
  )
Schule Blöcke Kapazität Kinder Auslastung Weg (min) Weg (Ø) Weg (max)
07G01 13 66 65.87 99.80% 10394.854 41208.35 67553.24
07G02 25 98 68.45 69.84% 9854.407 43308.79 95793.37
07G03 18 73 72.98 99.97% 5612.879 34905.84 67585.35
07G05 17 78 77.32 99.13% 2974.415 34113.15 55922.32
07G06 23 55 54.90 99.83% 14486.592 47310.14 84725.57
07G07 26 81 79.26 97.85% 6858.002 58310.50 126217.21
07G10 27 112 111.78 99.81% 11013.279 37674.58 87048.43
07G12 21 75 73.61 98.15% 7133.251 32159.58 126987.39
07G13 21 82 81.59 99.50% 11938.918 32602.53 59753.91
07G14 32 78 77.91 99.88% 4114.720 28625.73 65652.98
07G15 32 104 103.74 99.75% 10521.086 49633.38 90947.69
07G16 33 104 97.74 93.98% 11396.049 51016.56 165876.56
07G17 29 100 99.71 99.71% 1236.955 33875.89 111973.53
07G18 14 44 43.70 99.31% 4134.006 23755.78 46592.26
07G19 34 112 111.42 99.48% 4551.512 56389.17 130002.55
07G20 27 81 80.72 99.65% 1193.574 35844.04 86727.51
07G21 22 72 71.01 98.62% 13109.427 31728.63 76416.08
07G22 30 91 87.74 96.41% 8272.397 35984.90 81386.13
07G23 16 50 48.81 97.62% 6689.752 81418.59 152893.48
07G24 28 75 74.51 99.35% 7090.940 40827.13 118576.96
07G25 26 75 74.73 99.64% 5507.963 31427.67 69206.39
07G26 23 52 43.19 83.06% 5428.220 33540.96 101684.31
07G27 26 75 74.25 99.00% 2532.286 45702.87 98958.30
07G28 59 75 74.76 99.68% 9342.737 55027.04 121515.06
07G29 91 104 103.74 99.75% 1138.862 56844.63 123198.30
07G30 45 72 71.42 99.19% 482.926 54391.29 146048.88
07G31 24 78 68.22 87.46% 5582.570 59090.95 143043.15
07G32 59 78 77.88 99.84% 4557.764 49076.43 263035.55
07G34 38 100 95.34 95.34% 21120.242 72165.91 133903.62
07G35 38 75 71.75 95.67% 5838.956 47407.20 88698.65
07G36 43 100 96.48 96.48% 9295.770 69304.17 288785.86
07G37 52 69 68.99 99.99% 1399.929 80731.00 133766.21

Daten für das Tool

write_rds(solution, 'app/data/init_solution.rds')
write_rds(subset(blk, BEZ == bez_id), 'app/data/blocks.rds')
write_rds(subset(re_schulstand_df, spatial_name %in% relevant_schools), 'app/data/schools.rds')
write_rds(subset(bez, BEZ == bez_id), 'app/data/bez.rds')

block_stats = optim_kids_in_blks %>% inner_join(travel_from_blks, by=c('BLK'='BLK')) %>% inner_join(relevant_kapas, by=c('dst'='Schulnummer')) %>% inner_join(re_schulstand_df, by=c('dst'='spatial_name'))
write_rds(block_stats, 'app/data/block_stats.rds')
LS0tCnRpdGxlOiAiUiBOb3RlYm9vayIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKYGBge3IgbGlicywgaW5jbHVkZT1GLCB3YXJuaW5nPUZ9CmxpYnJhcnkocmVhZHIpCmxpYnJhcnkocmdkYWwpCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkodGlkeXIpCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShnZ21hcCkKbGlicmFyeShwdXJycikKbGlicmFyeShrbml0cikKbGlicmFyeShicm9vbSkKbGlicmFyeShtYXB0b29scykKYGBgCgpUbyBydW4gYW4gb3B0aW1pemF0aW9uIEkgbmVlZCBmb3Igb25lIGRpc3RyaWN0IChOZXVrw7ZsbG4/KQotIHJlc2lkZW50aWFsIGJ1aWxkaW5ncyB+PiBjaGlsZHJlbiBwZXIgYnVpbGRpbmcgfj4gY2hpbGRyZW4gYWdlIDUvNiBwZXIgYmxvY2sKLSBjYXBhY2l0eSBwZXIgc2Nob29sCgpMb2FkIGFsbCBiYXNlIGRhdGEKCmBgYHtyIGxvYWQgZGF0YX0Kc2FtcGxlZF9idWlsZGluZ3MgPSByZWFkX2Nzdignb3V0cHV0L3NhbXBsZWRfYnVpbGRpbmdzLmNzdicpCmJleiA9IHJlYWRPR1IoJ2Rvd25sb2FkL1JCU19PRF9CRVpfMjAxNV8xMi5nZW9qc29uJywgbGF5ZXIgPSAnT0dSR2VvSlNPTicpCmJsayA9IHJlYWRPR1IoJ2Rvd25sb2FkL1JCU19PRF9CTEtfMjAxNV8xMi5nZW9qc29uJywgbGF5ZXIgPSAnT0dSR2VvSlNPTicpCmxvciA9IHJlYWRPR1IoJ2Rvd25sb2FkL1JCU19PRF9MT1JfMjAxNV8xMi5nZW9qc29uJywgbGF5ZXIgPSAnT0dSR2VvSlNPTicpCnJlX3NjaHVsc3RhbmQgPSByZWFkT0dSKCdkb3dubG9hZC9yZV9zY2h1bHN0YW5kLmdlb2pzb24nLCBsYXllciA9ICdPR1JHZW9KU09OJykKYGBgCgpMb2FkIHRoZSBodWdlIHJvdXRlIG1hdHJpeAoKYGBge3J9CnJvdXRlbl9tYXRyaXggPSByZWFkX2Nzdignb3V0cHV0L3JvdXRlbl9tYXRyaXguY3N2JykgJT4lIG11dGF0ZShkaXN0YW5jZT1kaXN0YW5jZSo2MCkKYGBgCgpMb2FkIHNjaG9vbCBjYXBhY2l0aWVzIGFuZCBwb3B1bGF0aW9uIGRhdGEKClRPRE8gLSBuZXVlIERhdGVuIHZvbiBUb3JyZXMKCmBgYHtyfQprYXBhcyA9IHJlYWRfY3N2KCdkb3dubG9hZC9hbm1lbGRlemFobGVuLmNzdicpICU+JSBmaWx0ZXIoZ3JlcGwoJ0cnLCBTY2h1bG51bW1lcikpICU+JSBmaWx0ZXIoIWlzLm5hKGBQbMOkdHplYCkpCmVpbndvaG5lcl9sb3IgPSByZWFkX2RlbGltKCdkb3dubG9hZC9FV1IyMDE1MTJFX01hdHJpeC5jc3YnLCBkZWxpbT0nOycpCmBgYAoKIyMgV28gaGFiZW4gd2lyIEthcGF6aXTDpHRlbj8KCmBgYHtyfQpyZV9zY2h1bHN0YW5kX2RmID0gcmVfc2NodWxzdGFuZCAlPiUgYXMuZGF0YS5mcmFtZSgpICU+JSByZW5hbWUobG9uPWNvb3Jkcy54MSwgbGF0PWNvb3Jkcy54MikKcmVfc2NodWxzdGFuZF9kZiAlPiUgZmlsdGVyKGdyZXBsKCdHJywgc3BhdGlhbF9uYW1lKSkgJT4lIG11dGF0ZShCRVpJUks9ZW5jMnV0ZjgoYXMuY2hhcmFjdGVyKEJFWklSSykpKSAlPiUKICBncm91cF9ieShCRVpJUkspICU+JSBzdW1tYXJpc2UoYEFuemFobCBTY2h1bGVuYCA9IG4oKSkgJT4lCiAgcmVuYW1lKEJlemlyaz1CRVpJUkspICU+JSBsZWZ0X2pvaW4oa2FwYXMgJT4lIGdyb3VwX2J5KEJlemlyaykgJT4lIHN1bW1hcmlzZShgTWl0IEthcGF6aXTDpHRgID0gbigpKSkKYGBgCgpXaXIgd8OkaGxlbiBlaW5lbiBCZXppcmssIHdvIHdpciB2b24gYWxsZW4gU2NodWxlbiBLYXBhcyBoYWJlbi4KCmBgYHtyfQpyZV9zY2h1bHN0YW5kICU+JSBhcy5kYXRhLmZyYW1lKCkgJT4lIGZpbHRlcihncmVwbCgnRycsIHNwYXRpYWxfbmFtZSkpICU+JSBtdXRhdGUoQkVaSVJLPWVuYzJ1dGY4KGFzLmNoYXJhY3RlcihCRVpJUkspKSkgJT4lIGdyb3VwX2J5KEJFWklSSykgJT4lIHN1bW1hcmlzZShgQW56YWhsIFNjaHVsZW5gID0gbigpKSAlPiUKICByZW5hbWUoQmV6aXJrPUJFWklSSykgJT4lIGxlZnRfam9pbihrYXBhcyAlPiUgZ3JvdXBfYnkoQmV6aXJrKSAlPiUgc3VtbWFyaXNlKGBNaXQgS2FwYXppdMOkdGAgPSBuKCkpKSAlPiUgZmlsdGVyKGBBbnphaGwgU2NodWxlbmAgPT0gYE1pdCBLYXBheml0w6R0YCkKYGBgCgoKYGBge3J9CmJlemlyayA9ICdUZW1wZWxob2YtU2Now7ZuZWJlcmcnCnNjaHVsZW5fbWl0X2thcGEgPSBrYXBhcyAlPiUgZmlsdGVyKEJlemlyayA9PSBiZXppcmspICU+JSAuJFNjaHVsbnVtbWVyCnNjaHVsZW5fbWl0X2thcGEKZ3J1bmRzY2h1bGVuID0gcmVfc2NodWxzdGFuZCAlPiUgYXMuZGF0YS5mcmFtZSgpICU+JSBmaWx0ZXIoZ3JlcGwoJ0cnLCBzcGF0aWFsX25hbWUpKSAlPiUgZmlsdGVyKEJFWklSSyA9PSBiZXppcmspICU+JSAuJHNwYXRpYWxfbmFtZQonSW4gQW5tZWxkZWxpc3RlLCBmZWhsdCBpbiBTY2h1bHN0YW5kJwpzZXRkaWZmKHNjaHVsZW5fbWl0X2thcGEsIGdydW5kc2NodWxlbikKJ0luIHJlX3NjaHVsc3RhbmQsIGZlaGx0IGluIEFubWVsZGVsaXN0ZScKc2V0ZGlmZihncnVuZHNjaHVsZW4sIHNjaHVsZW5fbWl0X2thcGEpCmBgYAoKT01HIG51ciBiZWkgTWl0dGUgbWFwcHQgZGFzIHBlcmZla3QhIEFsbGUgYW5kZXJlbiBlcmZvcmRlcm4gTmFjaGZvcnNjaHVuZ2VuLgoKYGBge3J9Cm1hcCA9IGdldF9tYXAoJ0JlcmxpbicpCmBgYAoKCmBgYHtyfQojcmVfc2NodWxzdGFuZFtyZV9zY2h1bHN0YW5kJHNwYXRpYWxfbmFtZSAlaW4lIHNldGRpZmYoZ3J1bmRzY2h1bGVuLCBzY2h1bGVuX21pdF9hbm1lbGR1bmdlbiksXSAlPiUgVmlldygpCnJlX3NjaHVsc3RhbmRfZGYgPSByZV9zY2h1bHN0YW5kX2RmICU+JSBsZWZ0X2pvaW4oa2FwYXMsIGJ5PWMoJ3NwYXRpYWxfbmFtZSc9J1NjaHVsbnVtbWVyJykpCmBgYAoKYGBge3J9CmRhdGEgPSByZV9zY2h1bHN0YW5kX2RmICU+JSBmaWx0ZXIoZ3JlcGwoJ0cnLCBzcGF0aWFsX25hbWUpKSAlPiUgZmlsdGVyKEJFWklSSz09YmV6aXJrKSAlPiUgbXV0YXRlKGhhcy5jYXBhPWlzLm5hKGBQbMOkdHplYCkpCmdnbWFwKG1hcCkgKyBnZW9tX3BvaW50KGFlcyhsb24sIGxhdCwgY29sb3I9aGFzLmNhcGEpLCBkYXRhPWRhdGEpICsKICAgIGNvb3JkX21hcCh4bGltPWMobWluKGRhdGEkbG9uKS0wLjAxLCBtYXgoZGF0YSRsb24pKzAuMDEpLCB5bGltPWMobWluKGRhdGEkbGF0KS0wLjAxLCBtYXgoZGF0YSRsYXQpKzAuMDEpKQpgYGAKCklnbm9yaWVyZW4gZGFzIFByb2JsZW0gdW5kIHR1biBzbywgYWxzIG9iIG51ciBkaWVzZSBTY2h1bGVuIHJlbGV2YW50IHNpbmQ6CgpgYGB7cn0KcmVsZXZhbnRfc2Nob29scyA9IHJlX3NjaHVsc3RhbmRfZGYgJT4lIGZpbHRlcihncmVwbCgnRycsIHNwYXRpYWxfbmFtZSkpICU+JSBmaWx0ZXIoQkVaSVJLPT1iZXppcmsgJiAhaXMubmEoYFBsw6R0emVgKSkgJT4lIC4kc3BhdGlhbF9uYW1lCnJlbGV2YW50X3NjaG9vbHMKYGBgCgojIyBLaW5kZXIgaW0gQmV6aXJrIGF1ZiBHZWLDpHVkZSBhdWZ0ZWlsZW4KCjEuIExPUnMgaW4gTmV1a8O2bG4KMS4gRmluZGVuIGFsbGVyIFdvaG5nZWJlYsOkdWRlIGluIGRpZXNlbiBMT1JzCjIuIMOcYmVyIGBlaW53b2hsZXJfbG9yYCBmaW5kZW4gd2lyIGRpZSBkdXJjaHNjaG5pdHRsaWNoZSBLaW5kZXJ6YWhsIHBybyBfV29obmdlYsOkdWRlCgojIyMgTWFwcGluZyBCZXppcmstPkxPUi0+QmxvY2sKCmBgYHtyfQpkZl9iZXogPSBhcy5kYXRhLmZyYW1lKGJleikKZGZfbG9yID0gYXMuZGF0YS5mcmFtZShsb3IpCmRmX2JsayA9IGFzLmRhdGEuZnJhbWUoYmxrKQpgYGAKCiMjIyBMT1JzIGltIEJlemlyawoKYGBge3J9CmJlel9pZCA9IGZpbHRlcihkZl9iZXosIEJFWk5BTUUgPT0gYmV6aXJrKSRCRVoKcmVsZXZhbnRfbG9ycyA9IGRmX2xvciAlPiUgZmlsdGVyKEJFWiA9PSBiZXpfaWQpCnJlbGV2YW50X2Jsa3MgPSBkZl9ibGsgJT4lIGZpbHRlcihCRVogPT0gYmV6X2lkKQpgYGAKCgpgYGB7cn0KZ2dwbG90KCkgKyBnZW9tX3BhdGgoYWVzKHg9bG9uZywgeT1sYXQsIGdyb3VwPWdyb3VwKSwgZGF0YT1sb3JbbG9yJEJFWiA9PSBiZXpfaWQsXSkgKyBjb29yZF9tYXAoKSArIGdlb21fcGF0aChhZXMoeD1sb25nLCB5PWxhdCwgZ3JvdXA9Z3JvdXApLCBkYXRhPWJleiwgY29sb3I9J3JlZCcpCmBgYAoKIyMjIEJsw7Zja2UgaW0gQmV6aXJrCgpgYGB7cn0KZ2dwbG90KCkgKyBnZW9tX3BhdGgoYWVzKHg9bG9uZywgeT1sYXQsIGdyb3VwPWdyb3VwKSwgZGF0YT1ibGtbYmxrJEJFWiA9PSBiZXpfaWQsXSkgKyBjb29yZF9tYXAoKSArIGdlb21fcGF0aChhZXMoeD1sb25nLCB5PWxhdCwgZ3JvdXA9Z3JvdXApLCBkYXRhPWJleltiZXokQkVaID09IGJlel9pZCxdLCBjb2xvcj0ncmVkJykKYGBgCgoKIyMjIFdvaG5nZWLDpHVkZSBpbiBkaWVzZW4gTE9ScwoKU2Nob24gZXJsZWRpZ3QgaW4gYHNhbXBsZWRfYnVpbGRpbmdzYC4KCiMjIyBBbHRlcgoKVE9ETyBuZXVlIERhdGVuIHZvbiBUb3JyZXMKCmBgYHtyfQpyZWxldmFudF9ld3IgPSBlaW53b2huZXJfbG9yICU+JSBzZWxlY3QoUkFVTUlELCBFX0UwNl8wNykgJT4lIGZpbHRlcihSQVVNSUQgJWluJSByZWxldmFudF9sb3JzJFBMUikgJT4lCiAgbXV0YXRlKGtpZHM9YXMubnVtZXJpYyhnc3ViKCcsJywnLicsRV9FMDZfMDcpKSkgJT4lIGFzLmRhdGEuZnJhbWUoKQoKZGF0YSA9IHRpZHkobG9yW2xvciRCRVogPT0gYmV6X2lkLF0sIHJlZ2lvbj0nUExSJykgJT4lIGlubmVyX2pvaW4ocmVsZXZhbnRfZXdyLCBieT1jKCdpZCc9J1JBVU1JRCcpKQpnZ21hcChtYXApICsgZ2VvbV9wb2x5Z29uKGFlcyh4PWxvbmcsIHk9bGF0LCBncm91cD1ncm91cCwgZmlsbD1raWRzKSwgZGF0YT1kYXRhKSArCiAgY29vcmRfbWFwKHhsaW09YyhtaW4oZGF0YSRsb25nKS0wLjAxLCBtYXgoZGF0YSRsb25nKSswLjAxKSwgeWxpbT1jKG1pbihkYXRhJGxhdCktMC4wMSwgbWF4KGRhdGEkbGF0KSswLjAxKSkKYGBgCgojIyMgS2lkcyBpbiBCTEtzCgpGb3IgZWFjaCBMT1IgZGlzdHJpYnV0ZSB0aGUgY2hpbGRyZW4gYWNjb3JkaW5nIHRvIHRoZSBudW1iZXIgb2YgcGVvcGxlIGxpdmluZyBpbiB0aGF0IGJsb2NrCgpgYGB7cn0Ka2lkc19pbl9ibGtzID0gcmVsZXZhbnRfYmxrcyAlPiUgZ3JvdXBfYnkoUExSKSAlPiUgbXV0YXRlKEVpbndSYXRpbyA9IEVpbncvc3VtKEVpbncpKSAlPiUgdW5ncm91cCAlPiUgbGVmdF9qb2luKHJlbGV2YW50X2V3ciwgYnk9YygnUExSJz0nUkFVTUlEJykpICU+JSBtdXRhdGUoa2lkcyA9IEVpbndSYXRpbypraWRzKSAlPiUgc2VsZWN0KEJFWiwgUExSLCBCTEssIEVpbncsIGtpZHMpICU+JSBhcy5kYXRhLmZyYW1lKCkKcm93Lm5hbWVzKGtpZHNfaW5fYmxrcykgPSBraWRzX2luX2Jsa3MkQkxLCgpkYXRhID0gdGlkeShibGtbYmxrJEJFWiA9PSBiZXpfaWQsXSwgcmVnaW9uPSdCTEsnKSAlPiUgaW5uZXJfam9pbihraWRzX2luX2Jsa3MsIGJ5PWMoJ2lkJz0nQkxLJykpICU+JSBtdXRhdGUoa2lkcz1pZmVsc2Uoa2lkcz09MCwgTkEsIGtpZHMpLCBFaW53PWlmZWxzZShFaW53PT0wLCBOQSwgRWludykpCjAKZ2dtYXAobWFwKSArIGdlb21fcG9seWdvbihhZXMoeD1sb25nLCB5PWxhdCwgZ3JvdXA9Z3JvdXAsIGZpbGw9a2lkcyksIGRhdGE9ZGF0YSkgKwogIGNvb3JkX21hcCh4bGltPWMobWluKGRhdGEkbG9uZyktMC4wMSwgbWF4KGRhdGEkbG9uZykrMC4wMSksIHlsaW09YyhtaW4oZGF0YSRsYXQpLTAuMDEsIG1heChkYXRhJGxhdCkrMC4wMSkpCmBgYAoKIyMjIEthcGFzCgpgYGB7cn0KcmVsZXZhbnRfa2FwYXMgPSBrYXBhcyAlPiUgc2VsZWN0KFNjaHVsbnVtbWVyLCBLYXBhPWBQbMOkdHplYCkgJT4lIGZpbHRlcihTY2h1bG51bW1lciAlaW4lIHJlbGV2YW50X3NjaG9vbHMpICU+JSBhcy5kYXRhLmZyYW1lKCkKI3Jvdy5uYW1lcyhyZWxldmFudF9rYXBhcykgPSByZWxldmFudF9rYXBhcyRTY2h1bG51bW1lcgpgYGAKCiMjIyBTYW5pdHkKCmBgYHtyfQonU3VtbWUgS2FwYXMnCnJlbGV2YW50X2thcGFzICU+JSAuJEthcGEgJT4lIHN1bQonU3VtbWUgS2FwYXMgKyBleHRyYScKcmVsZXZhbnRfa2FwYXMgJT4lIC4kS2FwYV9leHRyYSAlPiUgc3VtCidBbm1lbGR1bmdlbicKa2FwYXMgJT4lIG11dGF0ZShBbm1lbGR1bmdlbiA9IGFzLm51bWVyaWMoZ3N1YignW14wLTldJywgJycsIEFubWVsZHVuZ2VuKSkpICU+JSBmaWx0ZXIoU2NodWxudW1tZXIgJWluJSByZWxldmFudF9zY2hvb2xzKSAlPiUgLiRBbm1lbGR1bmdlbiAlPiUgc3VtCidLaWRzIGxhdXQgU3RhdGlzdGlrJwpraWRzX2luX2Jsa3Mka2lkcyAlPiUgc3VtCnJlbGV2YW50X2V3ciRraWRzICU+JSBzdW0KYGBgCgojIyMgQ2FsY3VsYXRlIG1lYW4gZGlzdGFuY2VzIHRvIHNjaG9vbCBwZXIgYmxvY2sKCmZpbmQgYmxvY2sgb2YgZWFjaCByZXNpZGVudGlhbCBidWlsZGluZwoKYGBge3J9CnJlc2lkZW50aWFsX2J1aWxkaW5nc19ibG9ja3MgPSBzYW1wbGVkX2J1aWxkaW5ncyAlPiUgaW5uZXJfam9pbihkZl9ibGspICU+JSBmaWx0ZXIoQkVaID09IGJlel9pZCkKcmVzaWRlbnRpYWxfYnVpbGRpbmdzX2Jsb2NrcwpgYGAKCmBgYHtyfQpyb3V0ZXNfZnJvbV9ibGtzID0gcmVzaWRlbnRpYWxfYnVpbGRpbmdzX2Jsb2NrcyAlPiUKICBsZWZ0X2pvaW4ocm91dGVuX21hdHJpeCAlPiUgZmlsdGVyKGRzdCAlaW4lIHJlbGV2YW50X3NjaG9vbHMpLCBieT1jKCdPSSc9J3NyYycpKQpoZWFkKHJvdXRlc19mcm9tX2Jsa3MpCmBgYAoKIyMjIFdoaWNoIGJsb2NrcyBhcmUgcmVsZXZhbnQgYmVjYXVzZSB0aGVyZSBhcmUgcmVzaWRlbnRpYWwgYnVpbGRpbmdzIChUT0RPIGFuZCBzb21lb25lIGxpdmVzIHRoZXJlKQoKYGBge3J9CmRhdGEgPSB0aWR5KGJsa1tibGskQkVaID09IGJlel9pZCxdLCByZWdpb249J0JMSycpICU+JSBpbm5lcl9qb2luKHJvdXRlc19mcm9tX2Jsa3MgJT4lIGdyb3VwX2J5KEJMSykgJT4lIHN1bW1hcmlzZShuPW4oKSksIGJ5PWMoJ2lkJz0nQkxLJykpCmdnbWFwKG1hcCkgKyBnZW9tX3BvbHlnb24oYWVzKHg9bG9uZywgeT1sYXQsIGdyb3VwPWdyb3VwKSwgZmlsbD0ncmVkJywgZGF0YT1kYXRhKSArCiAgI2dlb21fcG9pbnQoYWVzKHg9bG9uLCB5PWxhdCksIGRhdGE9cmJfZGYsIGNvbG9yPSdibGFjaycsIHNpemU9MC4wMSkgKwogIGNvb3JkX21hcCh4bGltPWMobWluKGRhdGEkbG9uZyktMC4wMSwgbWF4KGRhdGEkbG9uZykrMC4wMSksIHlsaW09YyhtaW4oZGF0YSRsYXQpLTAuMDEsIG1heChkYXRhJGxhdCkrMC4wMSkpCmBgYAoKCmBgYHtyfQp0cmF2ZWxfZnJvbV9ibGtzID0gcm91dGVzX2Zyb21fYmxrcyAlPiUgYXMuZGF0YS5mcmFtZSgpICU+JSBncm91cF9ieShCTEssIGRzdCkgJT4lIHN1bW1hcmlzZShtaW49bWluKGRpc3RhbmNlKSwgYXZnPW1lYW4oZGlzdGFuY2UpLCBtZWQ9bWVkaWFuKGRpc3RhbmNlKSwgbWF4PW1heChkaXN0YW5jZSkpICU+JSB1bmdyb3VwCnRyYXZlbF9mcm9tX2Jsa3MKdHJhdmVsX2Zyb21fYmxrcyAlPiUgd3JpdGVfY3N2KCdvdXRwdXQvdHJhdmVsX2Zyb21fYmxrcy5jc3YnKQpgYGAKCkNvbG9yIEJsb2NrcyBieSBhdmcgZGlzdGFuY2UKYGBge3J9CmRhdGEgPSB0aWR5KGJsa1tibGskQkVaID09IGJlel9pZCxdLCByZWdpb249J0JMSycpICU+JSBsZWZ0X2pvaW4odHJhdmVsX2Zyb21fYmxrcyAlPiUgZ3JvdXBfYnkoQkxLKSAlPiUgdG9wX24oMSwgLWF2ZyksIGJ5PWMoJ2lkJz0nQkxLJykpCmdnbWFwKG1hcCkgKyBnZW9tX3BvbHlnb24oYWVzKHg9bG9uZywgeT1sYXQsIGdyb3VwPWdyb3VwLCBmaWxsPS1hdmcpLCBkYXRhPWRhdGEpICsKICBnZW9tX3BvaW50KGFlcyhsb24sIGxhdCksIGNvbG9yPSdyZWQnLCBkYXRhID0gcmVfc2NodWxzdGFuZF9kZiAlPiUgZmlsdGVyKEJFWklSSz09YmV6aXJrICYgU0NIVUxBUlQ9PSdHcnVuZHNjaHVsZScpKSArCiAgY29vcmRfbWFwKHhsaW09YyhtaW4oZGF0YSRsb25nKS0wLjAxLCBtYXgoZGF0YSRsb25nKSswLjAxKSwgeWxpbT1jKG1pbihkYXRhJGxhdCktMC4wMSwgbWF4KGRhdGEkbGF0KSswLjAxKSkKYGBgCgoKYGBge3J9CnRyYXZlbF9tYXRyaXggPSB0cmF2ZWxfZnJvbV9ibGtzICU+JSBzZWxlY3QoQkxLLCBkc3QsIGF2ZykgJT4lIHNwcmVhZChkc3QsIGF2ZykKZGltKHRyYXZlbF9tYXRyaXgpCnRyYXZlbF9tYXRyaXgKYGBgCgojIFNlbGVjdCByZWxldmFudCBkYXRhCgpgYGB7cn0Kb3B0aW1fa2FwYXMgPSByZWxldmFudF9rYXBhcwpvcHRpbV9raWRzX2luX2Jsa3MgPSBraWRzX2luX2Jsa3MgJT4lIGZpbHRlcihraWRzID4gMCkgJT4lIGlubmVyX2pvaW4odHJhdmVsX21hdHJpeCwgYnk9J0JMSycpICU+JSBzZWxlY3QoQkxLLCBraWRzKSAlPiUgbXV0YXRlKGtpZHM9a2lkcyowLjg4KQpucm93KG9wdGltX2tpZHNfaW5fYmxrcykKbnJvdyhvcHRpbV9rYXBhcykKCnNlbGVjdF9zY2hvb2xzID0gYXMuY2hhcmFjdGVyKG9wdGltX2thcGFzJFNjaHVsbnVtbWVyKQpzZWxlY3RfYmxrcyA9IGFzLmNoYXJhY3RlcihvcHRpbV9raWRzX2luX2Jsa3MkQkxLKQoKb3B0aW1fbWF0cml4ID0gaW5uZXJfam9pbihvcHRpbV9raWRzX2luX2Jsa3MsIHRyYXZlbF9tYXRyaXgsIGJ5PSdCTEsnKVtzZWxlY3Rfc2Nob29sc10KCmRpbShvcHRpbV9tYXRyaXgpCgpvcHRpbV9rYXBhcyRLYXBhICU+JSBzdW0Kb3B0aW1fa2lkc19pbl9ibGtzJGtpZHMgJT4lIHN1bQoKb3B0aW1fbWF0cml4ICU+JSB3cml0ZV9jc3YoJ291dHB1dC9vcHRpbV9tYXRyaXguY3N2JykKb3B0aW1fa2FwYXMgJT4lIHdyaXRlX2Nzdignb3V0cHV0L29wdGltX2thcGFzLmNzdicpCm9wdGltX2tpZHNfaW5fYmxrcyAlPiUgd3JpdGVfY3N2KCdvdXRwdXQvb3B0aW1fYmxrcy5jc3YnKQpgYGAKCiMjIyBOYWl2ZSBzb2x1dGlvbiAodGhhdCBkb2Vzbid0IG9iZXkgY2FwYWNpdHkgY29uc3RyYWludHMpCgpgYGB7cn0Kc29sdXRpb24gPSBvcHRpbV9tYXRyaXggJT4lIG11dGF0ZShCTEs9b3B0aW1fa2lkc19pbl9ibGtzJEJMSykgJT4lIGdhdGhlcihzY2hvb2wsIGRpc3QsIC1CTEspICU+JSBncm91cF9ieShCTEspICU+JSB0b3BfbigxLCAtZGlzdCkgJT4lIHVuZ3JvdXAKCm9wdGltX21hdHJpeCAlPiUgdCAlPiUgYXMuZGF0YS5mcmFtZSAlPiUgc3VtbWFyaXNlX2VhY2goZnVucyhtaW4pKSAlPiUgc3VtKCkKCnNvbGluZXMgPSByZV9zY2h1bHN0YW5kX2RmICU+JSBpbm5lcl9qb2luKHNvbHV0aW9uLCBieT1jKCdzcGF0aWFsX25hbWUnPSdzY2hvb2wnKSkgJT4lIGlubmVyX2pvaW4oY2JpbmQoYXMuZGF0YS5mcmFtZShjb29yZGluYXRlcyhibGtbYmxrJEJFWiA9PSBiZXpfaWQsXSkpLCBibGtbYmxrJEJFWiA9PSBiZXpfaWQsXUBkYXRhWydCTEsnXSkpCgpkYXRhID0gdGlkeShibGtbYmxrJEJFWiA9PSBiZXpfaWQsXSwgcmVnaW9uPSdCTEsnKSAlPiUgbGVmdF9qb2luKHNvbHV0aW9uLCBieT1jKCdpZCc9J0JMSycpKQpnZ21hcChtYXAsIGRhcmtlbiA9IGMoMC41LCAnd2hpdGUnKSkgKyBnZW9tX3BvbHlnb24oYWVzKHg9bG9uZywgeT1sYXQsIGdyb3VwPWdyb3VwLCBmaWxsPXNjaG9vbCksIGRhdGE9ZGF0YSkgKwogIGdlb21fc2VnbWVudChhZXMoeD1WMSx5PVYyLHhlbmQ9bG9uLHllbmQ9bGF0KSwgZGF0YT1zb2xpbmVzLCBzaXplPTAuMykgKwogIGdlb21fcG9pbnQoYWVzKGxvbiwgbGF0KSwgY29sb3I9J2JsYWNrJywgc2l6ZT0yLCBkYXRhID0gcmVfc2NodWxzdGFuZF9kZiAlPiUgaW5uZXJfam9pbihzb2x1dGlvbiwgYnk9Yygnc3BhdGlhbF9uYW1lJz0nc2Nob29sJykpKSArCiAgZ2VvbV9wb2ludChhZXMobG9uLCBsYXQsIGNvbG9yPXNwYXRpYWxfbmFtZSksIGRhdGEgPSByZV9zY2h1bHN0YW5kX2RmICU+JSBpbm5lcl9qb2luKHNvbHV0aW9uLCBieT1jKCdzcGF0aWFsX25hbWUnPSdzY2hvb2wnKSkpICsKICBjb29yZF9tYXAoeGxpbT1jKG1pbihkYXRhJGxvbmcpLTAuMDEsIG1heChkYXRhJGxvbmcpKzAuMDEpLCB5bGltPWMobWluKGRhdGEkbGF0KS0wLjAxLCBtYXgoZGF0YSRsYXQpKzAuMDEpKSArCiAgZ3VpZGVzKGNvbG9yPUYsIGZpbGw9RikKYGBgCgpgYGB7cn0Kc29sdXRpb24gJT4lIGlubmVyX2pvaW4odHJhdmVsX2Zyb21fYmxrcywgYnk9YygnQkxLJz0nQkxLJywgJ3NjaG9vbCc9J2RzdCcpKSAlPiUgc3VtbWFyaXNlX2VhY2goIm1heCIsIC1CTEssIC1zY2hvb2wsIC1kaXN0KQpgYGAKCiMgSnVNUAoKYGBgCmp1bGlhIGp1bXAuamwKYGBgCgpgYGB7cn0Kc29sdXRpb24gPSByZWFkX2Nzdignb3V0cHV0L3NvbHV0aW9uX2p1bXAuY3N2JykgJT4lIHNldE5hbWVzKC4sIHNlbGVjdF9zY2hvb2xzKSAlPiUKICBtdXRhdGUoQkxLPXNlbGVjdF9ibGtzKSAlPiUgZ2F0aGVyKHNjaG9vbCwgYXNzaWduZWQsIC1CTEspICU+JSBmaWx0ZXIoYXNzaWduZWQgPiAwKQoKc29saW5lcyA9IHJlX3NjaHVsc3RhbmRfZGYgJT4lIGlubmVyX2pvaW4oc29sdXRpb24sIGJ5PWMoJ3NwYXRpYWxfbmFtZSc9J3NjaG9vbCcpKSAlPiUgaW5uZXJfam9pbihjYmluZChhcy5kYXRhLmZyYW1lKGNvb3JkaW5hdGVzKGJsa1tibGskQkVaID09IGJlel9pZCxdKSksIGJsa1tibGskQkVaID09IGJlel9pZCxdQGRhdGFbJ0JMSyddKSkKCmRhdGEgPSB0aWR5KGJsa1tibGskQkVaID09IGJlel9pZCxdLCByZWdpb249J0JMSycpICU+JSBsZWZ0X2pvaW4oc29sdXRpb24sIGJ5PWMoJ2lkJz0nQkxLJykpCmdnbWFwKG1hcCwgZGFya2VuID0gYygwLjUsICd3aGl0ZScpKSArIGdlb21fcG9seWdvbihhZXMoeD1sb25nLCB5PWxhdCwgZ3JvdXA9Z3JvdXAsIGZpbGw9c2Nob29sKSwgZGF0YT1kYXRhKSArCiAgZ2VvbV9zZWdtZW50KGFlcyh4PVYxLHk9VjIseGVuZD1sb24seWVuZD1sYXQpLCBkYXRhPXNvbGluZXMsIHNpemU9MC4zKSArCiAgZ2VvbV9wb2ludChhZXMobG9uLCBsYXQpLCBjb2xvcj0nYmxhY2snLCBzaXplPTIsIGRhdGEgPSByZV9zY2h1bHN0YW5kX2RmICU+JSBpbm5lcl9qb2luKHNvbHV0aW9uLCBieT1jKCdzcGF0aWFsX25hbWUnPSdzY2hvb2wnKSkpICsKICBnZW9tX3BvaW50KGFlcyhsb24sIGxhdCwgY29sb3I9c3BhdGlhbF9uYW1lKSwgZGF0YSA9IHJlX3NjaHVsc3RhbmRfZGYgJT4lIGlubmVyX2pvaW4oc29sdXRpb24sIGJ5PWMoJ3NwYXRpYWxfbmFtZSc9J3NjaG9vbCcpKSkgKwogIGNvb3JkX21hcCh4bGltPWMobWluKGRhdGEkbG9uZyktMC4wMSwgbWF4KGRhdGEkbG9uZykrMC4wMSksIHlsaW09YyhtaW4oZGF0YSRsYXQpLTAuMDEsIG1heChkYXRhJGxhdCkrMC4wMSkpICsKICBndWlkZXMoY29sb3I9RiwgZmlsbD1GKQpgYGAKCmBgYHtyfQpzb2x1dGlvbiAlPiUgaW5uZXJfam9pbih0cmF2ZWxfZnJvbV9ibGtzLCBieT1jKCdCTEsnPSdCTEsnLCAnc2Nob29sJz0nZHN0JykpICU+JSBzdW1tYXJpc2VfYWxsKG1heCkKYGBgCgpgYGB7cn0Kc3VtKHJlYWRfY3N2KCdvdXRwdXQvc29sdXRpb25fanVtcC5jc3YnKSAqIG9wdGltX21hdHJpeCkKc3VtKHJlYWRfY3N2KCdvdXRwdXQvc29sdXRpb25fanVtcC5jc3YnKSAqIG9wdGltX21hdHJpeCAqIG9wdGltX2tpZHNfaW5fYmxrcyRraWRzKQpgYGAKCiMjIERpc3BsYXkKCmBgYHtyfQpsaWJyYXJ5KGZvcm1hdHRhYmxlKQpgYGAKCmBgYHtyfQpzb2x1dGlvbiAlPiUgaW5uZXJfam9pbihvcHRpbV9raWRzX2luX2Jsa3MsIGJ5PSdCTEsnKSAlPiUgaW5uZXJfam9pbih0cmF2ZWxfZnJvbV9ibGtzLCBieT1jKCdCTEsnPSdCTEsnLCAnc2Nob29sJz0nZHN0JykpICU+JQogIGdyb3VwX2J5KHNjaG9vbCkgJT4lIHN1bW1hcmlzZSgKICAgIGtpZHM9c3VtKGtpZHMpLAogICAgbnVtX2Jsb2Nrcz1uKCksCiAgICBtaW5fZGlzdD1taW4obWluKSwKICAgIGF2Z19kaXN0PW1lYW4oKGtpZHMqYXZnKS9zdW0oa2lkcykpLAogICAgbWF4X2Rpc3Q9bWF4KG1heCkKICApICU+JQogIGlubmVyX2pvaW4ocmVsZXZhbnRfa2FwYXMsIGJ5PWMoJ3NjaG9vbCc9J1NjaHVsbnVtbWVyJykpICU+JQogIG11dGF0ZSgKICAgIHV0aWxpemF0aW9uPWtpZHMvS2FwYQogICkgJT4lIHNlbGVjdCgKICAgU2NodWxlPXNjaG9vbCwKICAgYEJsw7Zja2VgPW51bV9ibG9ja3MsCiAgIEthcGF6aXTDpHQ9S2FwYSwKICAgS2luZGVyPWtpZHMsCiAgIEF1c2xhc3R1bmc9dXRpbGl6YXRpb24sCiAgIGBXZWcgKG1pbilgPW1pbl9kaXN0LAogICBgV2VnICjDmClgPWF2Z19kaXN0LAogICBgV2VnIChtYXgpYD1tYXhfZGlzdAogICkgJT4lCiAgZm9ybWF0dGFibGUoCiAgICBsaXN0KAogICAgICBLaW5kZXIgPSBmb3JtYXR0ZXIoInNwYW4iLCB4IH4gZGlnaXRzKHgsIDIpKSwKICAgICAgQXVzbGFzdHVuZyA9IGZvcm1hdHRlcigic3BhbiIsCiAgICAgICAgc3R5bGUgPSB4IH4gc3R5bGUoY29sb3IgPSBpZmVsc2UoeCA8IDEsICJncmVlbiIsICJyZWQiKSksCiAgICAgICAgeCB+IGljb250ZXh0KGlmZWxzZSh4IDwgMSwgIm9rIiwgInJlbW92ZSIpLCBwZXJjZW50KHgpKQogICAgICApLAogICAgICBgV2VnICjDmClgID0gcHJvcG9ydGlvbl9iYXIoImxpZ2h0Ymx1ZSIpLAogICAgICBgV2VnIChtaW4pYCA9IHByb3BvcnRpb25fYmFyKCJsaWdodGJsdWUiKSwKICAgICAgYFdlZyAobWF4KWAgPSBwcm9wb3J0aW9uX2JhcigibGlnaHRibHVlIikKICAgICkKICApCmBgYAoKCiMgRGF0ZW4gZsO8ciBkYXMgVG9vbApgYGB7cn0Kd3JpdGVfcmRzKHNvbHV0aW9uLCAnYXBwL2RhdGEvaW5pdF9zb2x1dGlvbi5yZHMnKQp3cml0ZV9yZHMoc3Vic2V0KGJsaywgQkVaID09IGJlel9pZCksICdhcHAvZGF0YS9ibG9ja3MucmRzJykKd3JpdGVfcmRzKHN1YnNldChyZV9zY2h1bHN0YW5kX2RmLCBzcGF0aWFsX25hbWUgJWluJSByZWxldmFudF9zY2hvb2xzKSwgJ2FwcC9kYXRhL3NjaG9vbHMucmRzJykKd3JpdGVfcmRzKHN1YnNldChiZXosIEJFWiA9PSBiZXpfaWQpLCAnYXBwL2RhdGEvYmV6LnJkcycpCgpibG9ja19zdGF0cyA9IG9wdGltX2tpZHNfaW5fYmxrcyAlPiUgaW5uZXJfam9pbih0cmF2ZWxfZnJvbV9ibGtzLCBieT1jKCdCTEsnPSdCTEsnKSkgJT4lIGlubmVyX2pvaW4ocmVsZXZhbnRfa2FwYXMsIGJ5PWMoJ2RzdCc9J1NjaHVsbnVtbWVyJykpICU+JSBpbm5lcl9qb2luKHJlX3NjaHVsc3RhbmRfZGYsIGJ5PWMoJ2RzdCc9J3NwYXRpYWxfbmFtZScpKQp3cml0ZV9yZHMoYmxvY2tfc3RhdHMsICdhcHAvZGF0YS9ibG9ja19zdGF0cy5yZHMnKQpgYGAKCg==